#pragma once

#include <iostream>
#include <type_traits>
#include <utility>
#include <algorithm>
#include <functional>

namespace GenericLib
{
    /**
     * 11、通用库
     * 11.1.可调用类型
     * 11.2.实现通用库
     * 11.3.完美转发临时变量
     * 11.4.模板参数的引用
     * 11.5.缓式评估
     * 11.6.编写泛型库
     */

    /**
     * 11.1. 可调用类型
     *      函数对象类型：
     *          • 函数指针类型
     *          • 具有重载 operator()(函数操作符，有时称为函子) 的类类型，包括 Lambda
     *          • 使用转换函数生成指向函数的指针或指向函数引用的类类型
     * 11.1.1 函数对象
     */

    /**
     * 11.1.1 函数对象
     * 将函数名作为函数参数传递时，实际上传递的不是函数本身，而是指向函数的指针或引用。
     */

    // foreach 实现：
    template <typename Iter, typename Callable>
    void myForeach(Iter current, Iter end, Callable op)
    {
        while (current != end)
        {
            op(*current);
            ++current;
        }
    }

    // 普通函数，直接传入Callable后会decay成函数指针
    void myPrint(int num)
    {
        std::cout << num << std::endl;
    }

    // 仿函数
    struct MyPrint
    {
        void operator()(int num)
        {
            std::cout << num << std::endl;
        }
    };

    /**
     * 11.1.2 处理成员函数和附加函数
     * std::invoke() since cpp17
     *      可调用对象是指向成员的指针，则使用第一个附加参数作为 this 对象。
     *      否则，所有附加参数都只是作为参数传递给可调用对象
     */
    template <typename Iter, typename Callable, typename... Args>
    void myForeach2(Iter current, Iter end, Callable op, Args... args)
    {
        while (current != end)
        {
            std::invoke(op,        // 成员函数
                        args...,   // 附加参数
                        *current); // 当前元素
            ++current;
        }
    }
    /*
    1、成员函数指针：
    GenericLib::MyForeach2 my2;
    GenericLib::myForeach2(
            std::begin(vec),std::end(vec),
            &GenericLib::MyForeach2::print, // 传入类的成员参数
            my2 // 将类的成员作为args（附加参数传入）传入
            // *current = int
        );
    其中 std::invoke(op,args...)为：
        std::invoke(&GenericLib::MyForeach2::print, my2,*current);

    2、 普通函数，比如lambda
    GenericLib::myForeach2(std::begin(vec), std::end(vec), [](const std::string &value, int num)
                        { std::cout << value << ": " << num << std::endl; }, "lambda");
    其中 std::invoke(op,args...)为：
        std::invoke(struct_lambda{}(value,*current)); // struct_lambda{} 指代lambda表达式编译成 重载operator() 类后实例成的对象
     */

    struct MyForeach2
    {
        int print(int num)
        {
            std::cout << "member function: " << num << std::endl;
            return 1;
        }
    };

    /**
     * 11.1.3 使用包装函数
     * std::invoke() 的一个常见应用是封装单个函数调用
     * 这里返回值需要使用 decltype(auto) ，这是C++14的一个占位符
     *      用 auto&& 声明 ret 并不正确。作为一个引用，
     *      auto&& 扩展返回值的生命周期直到作用域结束 (参见第 11.3 节)，但不超出函数调用者的返回语句。
     */
    template <typename Callable, typename... Args>
    decltype(auto) call(Callable &&op, Args &&...args)
    {
        decltype(auto) ret{std::invoke(
            std::forward<Callable>(op),
            std::forward<Args>(args)...)};
        return ret;
    }

    // 另外一种跟编译时if constexpr结合的call，可以防止decltype(auto) == void时的情况
    // 获取返回值类型的两种方法：
    //      1、std::invoke_result_t<Callable,Args...> // since C++17
    //      2、typename std::result_of<Callable(Args...)>::type // since C++11
    //         std::result_of_t<Callable(Args...)> // since C++14
    //      3、std::decay_t<decltype(std::declval<Callable>()(std::declval<Args>()...))>：配合std::decay_t可以得到衰退的类型
    template <typename Callable, typename... Args>
    decltype(auto) callIfConstexpr(Callable &&op, Args &&...args)
    {
        // if constexpr (std::is_same_v<std::invoke_result_t<Callable, Args...>, void>) // since C++17
        if constexpr (std::is_same_v<std::result_of_t<Callable(Args...)>, void>) // since C++14
        {
            // return type is void
            std::invoke(std::forward<Callable>(op), std::forward<Args>(args)...);
            return;
        }
        else
        {
            // return type is not void
            decltype(auto) ret{
                std::invoke(std::forward<Callable>(op), std::forward<Args>(args)...)};
            return ret;
        }
    }
    
    /*
    // 将 decltype、std::declval<Type>() 放在template中声明的情况，注意Args的声明需要使用std::declval进行展开折叠
    // 使用了std::declval进行两次获取右值，一次是Callable，一次是Args
    template <typename Callable, typename... Args,
              typename = std::enable_if_t<
                    std::is_convertible_v<decltype(std::declval<Callable>()(std::declval<Args>()...)), int>>>
    decltype(auto) callIfConstexpr(Callable &&op, Args &&...args)
    {
        std::decay_t<decltype(std::declval<Callable>()(args...))> ret{
            std::invoke(std::forward<Callable>(op),
                        std::forward<Args>(args)...)};
        return ret;
    }
    */

    // 使用 decltype()、std::declval<Type>() 得到函数返回值的类型，使用 std::declval<Type>() 获取 Type&&，
    // (Type&&)(args...) 调用函数得到返回值ReturnVal，
    // 使用 std::decay_t<decltype(ReturnVal)> 得到返回值的类型，同时进行返回值类型衰退
    // template <typename Callable, typename... Args>
    // decltype(auto) callIfConstexpr(Callable &&op, Args &&...args)
    // {
    //     std::decay_t<decltype(std::declval<Callable>()(args...))> ret{
    //         std::invoke(std::forward<Callable>(op),
    //                     std::forward<Args>(args)...)};
    //     return ret;
    // }

    /**
     * 11.2. 实现通用库
     * std::invoke() 只是标准库为实现泛型库提供的有用实用程序的一个例子。
     *      接下来，将看看其他一些重要的特性。
     */
    /**
     * 11.2.1 类型特征
     * 标准库提供了各种类型特征工具，允许计算和修改类型。
     */
    template <typename T>
    void printType(T &&type)
    {
        if constexpr (std::is_array_v<type>)
        {
            std::cout << "is array" << std::endl;
        }
        if constexpr (std::is_reference_v<type>)
        {
            std::cout << "is refernec" << std::endl;
        }
        if constexpr (std::has_virtual_destructor_v<type>)
        {
            std::cout << "has virtual destructor" << std::endl;
        }
        // ......
    }
    /* 一些其他操作
    std::remove_const_t<std::remove_reference_t<int const&>> // int
    std::remove_reference_t<std::remove_const_t<int const&>> // int const

    std::decay_t<int const&> // yields int

    make_unsigned_t<int> // unsigned int
    make_unsigned_t<int const&> // undefined behavior (hopefully error

    add_rvalue_reference_t<int> // int&&
    add_rvalue_reference_t<int const> // int const&&
    add_rvalue_reference_t<int const&> // int const& (lvalue-ref remains lvalue-ref) 与引用折叠有关

    is_copy_assignable_v<int> // yields true (generally, you can assign an int to an int)
    is_assignable_v<int,int> // yields false (can’t call 42 = 42)
    is_assignable_v<int&,int&> // yields true

    is_swappable_v<int> // yields true (assuming lvalues)
    is_swappable_v<int&,int&> // yields true (equivalent to the previous check)
    is_swappable_with_v<int,int> // yields false (taking value category into account)
     */

    /**
     * 11.2.2 std::addressof()
     * std::addressof<>() 函数模板生成对象或函数的实际地址。
     * 即使对象类型有重载操作符 &，也能工作。尽管后者很少使用，但可能会使用 (例如，在智能指针中)。
     * 因此，如果需要任意类型对象的地址，建议使用 addressof()
     */

    template <typename T>
    void testAddressof(T &&t)
    {
        // 传入智能指针时，获取的是智能指针对象的地址，而不是指针所指向对象的地址
        auto p = &t; // 如果对象重载了&操作符，可能会导致失败
        auto q = std::addressof(t);
    }
    /**
     * 11.2.3 std::declval()
     * std::declval<>() 函数模板可以用作特定类型的对象引用的占位符。
     * std::declval<T>() 用于在编译时生成一个指定类型的右值引用。 ！！！
     *      它的主要作用是帮助进行类型推导和编译时检查，而不会在运行时产生任何实际的对象。
     */

    /* std::declval<T>() 的实现
        1、可以仅声明，但不定义：
            函数体是运行时调用的，而 std::declval<T>() 是在编译时只用于判断类型，可以只声明，用于编译期推导返回值类型
            不允许在运行时调用，declval函数体直接static_assert(false)。
        2、static_assert(bool,error_msg) 编译时判断是否满足条件，不满足则编译失败

    template<typename _Tp, typename _Up = _Tp&&>
    _Up
    __declval(int);

    template<typename _Tp>
    _Tp
    __declval(long);

    // 编译时调用 std::declval<T>() 只是用于推导类型，
    //   即调用下面这个函数声明，而不调用具体的函数体，所以不会触发 static_assert !!!
    template<typename _Tp>
    auto declval() noexcept -> decltype(__declval<_Tp>(0));

    template<typename _Tp>
    struct __declval_protector
    {
      static const bool __stop = false;
    };

    // 运行时如果直接调用 std::declval<T>() 会调用这个函数，直接报错。如：auto x = std::declval<int>(); // 报错
    template<typename _Tp>
    auto declval() noexcept -> decltype(__declval<_Tp>(0))
    {
      static_assert(__declval_protector<_Tp>::__stop,
            "declval() must not be used!");
      return __declval<_Tp>(0);
    }
     */

    /*

    std::decay_t<decltype(true?std::declval<T1>():std::declval<T2>())> 的作用在于：
        1、t1 > t2 ? t1 : t2 的行为依赖于 C++ 的隐式类型提升规则。例如：
            当比较 int 和 double 时，int 会被提升为 double，因此返回值类型是 double。
            如果比较 char 和 int，char 会被提升为 int。
        2、使用std::decay_t<>:
            确保返回值的类型不是引用、不是const、数组和函数会被退化会指针
        3、可以在加入enable_if_t进行判断，确保T1、T2两个类型可以进行compare
     */

    template <typename T1, typename T2>
    constexpr bool is_comparable_v = std::is_convertible_v<bool, decltype(true ? std::declval<T1>() : std::declval<T2>())>;

    template <typename T1, typename T2,
              typename RT = std::decay_t<decltype(true ? std::declval<T1>() : std::declval<T2>())>,
              typename = std::enable_if_t<is_comparable_v<T1, T2>>>
    RT max(T1 t1, T2 t2)
    {
        return t1 > t2 ? t1 : t2;
    }

    // 类似 std::declval<T>() 的实现例子：
    /*
    decltype() 仅用于类型推导，不会触发模板函数的实例化，因此不会检查 static_assert 的条件。
        在 static_assert(std::is_reference_v<T>, "error"); 中不会对std::is_reference_v<T>进行实例化
        但是在 static_assert(false,"error"); 中则会出现编译时异常
     */
    template <typename T>
    auto test() -> std::decay_t<T>;

    template <typename T>
    struct test_protector
    {
        static const bool stop = false;
    };

    // static_assert(false,...); 会产生编译时异常
    // template<typename T>
    // auto test()->std::decay_t<T>
    // {
    //     static_assert(false,"error");
    // }
    template <typename T>
    auto test() -> std::decay_t<T>
    {
        // 以下两中 static_assert 都可以实现运行时 static_assert
        // static_assert(test_protector<T>::stop,"error");
        static_assert(std::is_reference_v<T>, "error");
        return std::decay_t<T>{};
    }

    /**
     * 11.4. 模板参数的引用
     * 如果将引用传入模板参数中，会有意想不到的后果，所以可能需要禁止传入引用
     * 禁用传入引用的方法：
     *  1、使用static_assert()静态断言禁止传入引用
     *  2、使用std::enable_if_t<>
     */
    template <typename T, std::size_t SZ, typename = std::enable_if_t<!std::is_reference_v<T>>> // std::enable_if_t<>
    std::vector<T> getVector()
    {
        static_assert(!std::is_reference_v<T>, "T can't be reference!"); // 静态断言
        return std::vector<T>(SZ);                                       // 如果传入的是 int&，引用初始化会报错
    }

    /**
     * 11.5. 缓式评估
     *      使代码是否能够处理不完整的类型
     */
    template <typename T>
    class Cont
    {
    private:
        T *value;

    public:
        /*
        // is_move_constructible_v 要求T类型是个完整类型。因此不延迟实例化，在struct Node定义中会报错。
        std::conditional_t<std::is_move_constructible_v<T>, T &&, T &> foo()
        {
            // 特性依赖于模板参数D(默认为T，我们想要的值)，编译器必须等到foo()调用是才会实例化foo成员函数
            // 通过这种方式，可以实现 is_move_constructible_v 延迟实例化
        }
         */

        template <typename D = T>
        std::conditional_t<std::is_move_constructible_v<T>, T &&, T &> foo()
        {
            // 特性依赖于模板参数D(默认为T，我们想要的值)，编译器必须等到foo()调用是才会实例化foo成员函数
            // 通过这种方式，可以实现 is_move_constructible_v 延迟实例化
        }
    };

    struct Node
    {
        std::string value;
        Cont<Node> next; // 此时Node不是完整类型，可能会出现异常
    };

    /**
     * 11.6. 编写泛型库：一个对编写规则的小总结！！！最好熟悉一下
     * • 模板中使用转发引用来转发值 (参见第 91 页 6.1 节)。如果值不依赖于模板参数，使用auto&&(参见 11.3 节)。
     * • 当参数声明为转发引用时，模板参数在传递左值时要有引用类型 (参见 15.6.2 节)。
     * • 当需要依赖于模板形参的对象地址时，使用 std::addressof()，
     *      以避免当对象绑定到带有重载操作符 & 的类型时出现意外 (11.2.2 节)
     * • 对于成员函数模板，确保不会比预定义的复制/移动构造函数或赋值操作符更好地匹配 (6.4节)。
     * • 模板参数可能是字符串字面值，且不通过值传递时 (7.4 节和 D.4 节)，请考虑使用 std::decay。
     * • 如果模板参数有 out 或 inout，请准备好处理参数可能指定为 const 类型的情况 (参见 7.2.2 节)。
     * • 准备好处理模板参数引用的副作用 (参见 11.4 节了解详细信息，19.6.1 节为示例)。
     *      特别是，要确保返回类型不能是引用 (参见 7.5 节)。
     * • 准备好处理不完全类型，从而进行以支持，例如：递归数据结构 (参见 11.5 节)。
     * • 重载所有数组类型，而不仅仅是 T[SZ](参见 5.4 节)。
     */

};